# @Time : 2024-11-14 17:59
# @Author : Fioman 
# @Phone : 13149920693
"""
eap客户端和服务端的操作类,和外部的交互接口
"""
import threading
import time
from datetime import datetime, timedelta

import gevent
from flask import Flask, jsonify, request

import settings
from db_tools.db_handler import DbHandler
from eap_control import eap_tool, eap_request, response_parse
from eap_control.change_system_time import set_system_time
from eap_control.data_model import LotPortType, DataResult, DownloadType, ClockType
from eap_control.eap_config import EapConfig
from eap_control.eap_debug import debug_eap
from eap_control.eap_tool import get_now
from tools.send_to_ui_tools import (update_eap_status_to_ui, update_cim_message,
                                    update_prodinfo_download_to_ui, update_lot_command_download_to_ui, update_current_user_id)

ec = EapConfig()
db = DbHandler()

app = Flask(__name__)


@app.route('/api/<method>', methods=['POST'])
def api_method(method):
    """
    处理Post请求
    @param method:请求的方法名称
    @return:
    """
    try:
        if 'application/json' not in request.content_type:
            debug_eap(f"request.content_type Error: 接收类型: {request.content_type}")
            return jsonify({'error': 'Content-Type must be application/json'}), 400
        data = request.json
        debug_eap(f"从Eap接收到请求: method={method}, data={data}")
        response = EapHandler.deal_eap_request(method, data)
        if response is None:
            debug_eap(f"in api_method() 未处理的Eap请求消息: {method},响应消息为None")
        else:
            debug_eap(f"Send {method} 的 response: {response}")
        return jsonify(response)
    except Exception as e:
        debug_eap(f"in api_method: {str(e)}")
        return jsonify({"error": str(e)}), 400


class EapHandler:
    cim_message_update_flag = False
    screenCode = "0"
    bigCutterZmc = None

    def __init__(self, bigCutterZmc):
        EapHandler.bigCutterZmc = bigCutterZmc
        self.requestThread = threading.Thread(target=self.request_listener, daemon=True)
        self.recvThread = threading.Thread(target=self.flask_app_start, daemon=True)
        self.utilityReportThread = threading.Thread(target=self.utility_report_task, daemon=True)
        self.rtmReportThread = threading.Thread(target=self.rtm_report_task,daemon=True)
        self.shiftIdReport01 = False  # 1号班别是否已经上报
        self.shiftIdReport02 = False  # 2号班别是否已经上报

    @staticmethod
    def rtm_report_task():
        """
        即时监控资料上报任务,每隔180s上报一次
        :return:
        """
        while True:
            response = eap_request.request_rtm_report()
            gevent.sleep(float(ec.RTMDuration))
            


    @staticmethod
    def ui_btn_close_cim_message_modal():
        """
        界面上点击了关闭远程讯息
        :return:
        """
        if EapHandler.screenCode == "0":
            EapHandler.cim_message_update_flag = False
        else:
            EapHandler.bigCutterZmc.close_buzzer_alarm()

    def utility_report_task(self):
        """
        统计资料上报监听任务,每隔2分钟检查一次,如果在时间范围内,就上报对应的统计资料
        @return:
        """
        shiftId = None
        while True:
            targetTime = datetime.strptime(ec.end01, "%H:%M").time()
            currentTime = datetime.now().time()
            timePlus = (datetime.combine(datetime.today(), targetTime) + timedelta(minutes=5)).time()
            # 判断时间是否超过了这个时间5分钟
            if targetTime < currentTime < timePlus :
                if not  self.shiftIdReport01:
                    # 如果超过了5分钟
                    debug_eap("1号班别进行上报")
                    shiftId = "1"
                    self.shiftIdReport01 = True
            else:
                self.shiftIdReport01 = False

            targetTime = datetime.strptime(ec.end02, "%H:%M").time()
            timePlus = (datetime.combine(datetime.today(), targetTime) + timedelta(minutes=5)).time()
            if targetTime < currentTime < timePlus:
                if not  self.shiftIdReport02:
                    debug_eap("2号班别进行上报")
                    shiftId = "2"
                    self.shiftIdReport02 = True
                else:
                    self.shiftIdReport02 = False
            else:
                self.shiftIdReport02 = False

            if shiftId is not None:
                ec.shiftId = shiftId
                response = eap_request.request_utility_report()
                ec.shiftId = None
                shiftId = None
                # 上报完之后,要将运行时间和稼动率清零
                newData = {
                    "totalRunTimes":0,
                    "useTimes":0
                }
                from tools.config_params import ConfigParams
                cp = ConfigParams()
                cp.totalRunTimes = 0
                cp.useTimes = 0
                db.save_config_params(newData)
                result = response_parse.parse_utility_report_response(response)
                if not result.state:
                    debug_eap(result.info)
            time.sleep(2.5)

    @staticmethod
    def request_listener():
        while settings.IS_MAIN_ALIVE:
            response = eap_request.request_are_you_there()
            result = response_parse.parse_are_you_there_response(response)
            if not result.state:
                debug_eap(f"are_you_there Error: {result.info}")
            # 当状态发生改变的时候,才会去进行状态更新
            if result.state != EapConfig.eapOnline:
                EapConfig.eapOnline = result.state
                if EapConfig.eapOnline:
                    debug_eap(f"检测到Eap在线,上报当前设备的状态!")
                    ec.realStatusData.real_status_report()
                else:
                    debug_eap(f"检测到Eap离线!")
                update_eap_status_to_ui(EapConfig.eapOnline)
            time.sleep(ec.askDuration)

    @staticmethod
    def flask_app_start():
        app.run(host="0.0.0.0", port=ec.eqpPort)

    @classmethod
    def deal_eap_request(cls, methodName, data):
        if methodName == "AreYouThere":
            response = eap_tool.get_response_to_eap(methodName)
        elif methodName.lower() == "CIMMessage".lower():
            if ec.cimMode.lower() == "remote":
                cls.deal_cim_message_data(data)
                response = eap_tool.get_response_to_eap(methodName)
        elif methodName.lower() == "DateTimeCalibration".lower():
            if ec.cimMode.lower() == "remote":
                opRes = cls.deal_datetime_calibration(data)
                bodyData = {
                    "now": eap_tool.get_now(),
                    "ip": ec.eqpIp
                }
                if opRes.state:
                    response = eap_tool.get_response_to_eap(methodName, body=bodyData)
                else:
                    response = eap_tool.get_response_to_eap(methodName,result="NG",rtnMsg=opRes.info,
                                                            body= bodyData)
        elif methodName.lower() == "LotCommandDownload".lower():
            if ec.cimMode.lower() == "remote":
                dealResult = cls.deal_lot_command_download(data)
                response = eap_tool.get_response_to_eap(methodName)
            else:
                debug_eap(f"LotCommandDownload 请求,CIMMode为Local,不进行处理")
                response = eap_tool.get_response_to_eap_error(methodName)
        elif methodName.lower() == "ProductionInfoDownload".lower():
            if ec.cimMode.lower() == "remote":
                result = cls.deal_production_info_download(data)
                body = {
                    "lot_id": result.data
                }
                if result.state:
                    response = eap_tool.get_response_to_eap(methodName, body=body)
                else:
                    response = eap_tool.get_response_to_eap(methodName, result="NG",
                                                            rtnMsg=result.info)
                return response
        elif methodName.lower() == "UserLoginRequest".lower():
            if ec.cimMode.lower() == "remote":
                result = cls.deal_user_login_request(data)
                resBody  =result.data
                response = eap_tool.get_response_to_eap(methodName,body=resBody)
        else:
            response = None
        return response

    def start_thread(self):
        """
        请求开启请求和接收请求的线程.
        一个线程,主要用来每隔一段时间去发送are_you_there_reqeust.
        另外一个线程主要用来接收请求
        @return:
        """
        if not self.requestThread.is_alive():
            self.requestThread.start()
        if not self.recvThread.is_alive():
            self.recvThread.start()
        if not self.utilityReportThread.is_alive():
            self.utilityReportThread.start()
        if not self.rtmReportThread.is_alive():
            self.rtmReportThread.start()

    @classmethod
    def deal_cim_message_data(cls, data):
        """
        处理cim_message消息,将远程讯息显示到桌面,有两种情况一种是固定时间关闭,一种是必须要手动关闭
        @param data:
        @return:
        """
        try:
            request_body = data.get("request_body")
            screen_control = request_body.get("screen_control", "None")
            screen_code = request_body.get("screen_code", "None")
            EapHandler.screenCode = screen_code
            constant_time = request_body.get("constant_time", "None")
            cim_msg = request_body.get("cim_msg", "None")
            request_head = data.get("request_head")
            time_stamp = request_head.get("time_stamp", get_now())
            debug_eap(f"接收到远程讯息: {request_body}")
            cimMessage = cim_msg
            data = {
                "key": int(time.time() * 1000),
                "screen_control": screen_control,
                "screen_code": screen_code,
                "cimMessage": cimMessage,
                "constant_time": constant_time,
                "timeStamp": time_stamp,
            }
            db.insert_cim_message(data)
            cls.bigCutterZmc.open_buzzer_alarm()
            if screen_code != "0":
                leftTime = "never"
                # 打开蜂鸣器叫
            else:
                leftTime = int(constant_time)
            update_cim_message(screen_code, cimMessage, time_stamp, leftTime)
            # 如果screen_control 为 "0" 表示一定的时间内,自动弹窗自动消失,如果为"1"表示要手动关闭弹窗
            if screen_control == "0":
                # 创建一个线程去关闭远程讯息
                threading.Thread(target=cls.close_cim_message_handler,
                                 args=(int(constant_time), screen_code, cimMessage, time_stamp)).start()
        except Exception as e:
            debug_eap("in  deal_cim_message_data() error: {}".format(str(e)))
            
    @staticmethod
    def close_cim_message_handler(delayTime, screen_code, cimMessage, time_stamp):
        EapHandler.cim_message_update_flag = True
        for i in range(1, delayTime):
            if not EapHandler.cim_message_update_flag:
                break
            gevent.sleep(1)
            update_cim_message(screen_code, cimMessage, time_stamp, delayTime - i)

        debug_eap(f"经过了 {delayTime} 秒之后,关闭远程讯息框")
        EapHandler.bigCutterZmc.close_buzzer_alarm()
        update_cim_message(None, None, None, None)

    @classmethod
    def deal_datetime_calibration(cls, data) -> DataResult:
        """
        处理时间校准的请求任务
        @param data:
        @return:
        """
        opRes = DataResult()
        try:
            request_body = data.get("request_body")
            now = request_body.get("now")
            ip = request_body.get("ip")
            debug_eap(f"接收到时间校准的消息请求: {request_body},"
                      f"校准时间: {now},ip:{ip}")
            return set_system_time(now)
        except Exception as e:
            debug_eap("in  deal_cim_message_data() error: {}".format(str(e)))
            opRes.info   = "in  deal_cim_message_data() error: {}".format(str(e))
            return opRes
    @classmethod
    def deal_lot_command_download(cls, data):
        """
        解析
        
        
        @param data:
        @return:
        """
        result = DataResult()
        try:
            data = data.get("request_body")
            portNo = data.get("port_no")
            portType = data.get("port_type")
            carrierId = data.get("carrier_id")
            portCommand = data.get("port_command")
            lotInfos = data.get("lot_infos")
            lotList = lotInfos.get("lot")
            data = {
                "port_no": portNo,
                "port_type": portType,
                "carrier_id": carrierId,
                "port_command": portCommand,
            }
            for lot in lotList:
                lotId = lot.get("lot_id")  # 批次号
                lotQty = lot.get("lot_qty")  # 数量
                data["lot_id"] = lotId
                data["lot_qty"] = lotQty
                # 看看是否在prodDownloadTable中,只有在的工单才是有效的工单
                res, orderData = db.get_data_list(db.eapProdDownloadTable, query={"lot_id": lotId})
                if res != "ok":
                    debug_eap(f"lot_id: {lotId} 不在eapProdDownloadTable,此工单Lot Command失败")
                    continue
                if portCommand == LotPortType.Start.name:
                    taskOrder = orderData[0]
                    taskOrder["key"] = int(time.time() * 1000)
                    taskOrder["finished"] = 0  # 完成数量
                    taskOrder["download_time"] = get_now()  # 工单下载时间
                    time.sleep(0.1)  # 按照时间插入,所以最后就是key越小,越靠前
                    # 将eapProdDownloadTable的工单数据
                    # 在prod里面将工单删除
                    db.delete_download_info(query={"lot_id": lotId})
                    # 然后添加到lot_command_download里面去
                    db.insert_lot_command_download(taskOrder)
                    # 在界面上进行显示download_info
                    update_prodinfo_download_to_ui()
                    # 在界面上显示lotCommand的数量
                    update_lot_command_download_to_ui()
                    if not settings.eapIsInProduce:
                        # 进行工单切换的操作,让自动作业那里去切换去,切换的时候,就去
                        settings.eapCanChangeOrderFlag = True
                        debug_eap("eapIsInProduce 为False,可以切换工单,进行工单切换操作")
                    else:
                        debug_eap("eapIsInProduce 为True,不能切换工单")
                else:
                    db.delete_lot_command_download(query={"lot_id": lotId})
            # 更新前五个生产的列表,并且如果当前正在生产的lot_id不一致的时候,要进行切换为当前要生产的lot
            # 向Eap发送消息 EquipmentCurrentLotReport
            ec.reload_prod_lot_list()
        except Exception as e:
            debug_eap("in deal_lot_command_download() error: {}".format(str(e)))

    @classmethod
    def deal_user_login_request(cls, data):
        """
        处理user_login_request消息,处理用户登录请求
        @param data:
        @return:
        """
        result = DataResult()
        try:
            data = data.get("request_body")
            clock_type = data.get("clock_type")
            user_id = data.get("user_id")
            user_name = data.get("user_name")
            dept_id = data.get("dept_id")
            shift_id=data.get("shift_id")
            nick_name = data.get("nick_name")

            # 这里要执行什么命令呢? 登录或者登出指令
            if clock_type == ClockType.I.name:
                # 1. 如果当前用户不是空,先登出
                if ec.userId != "":
                    response = eap_request.request_user_verify(ec.userId,ClockType.O.name)
                    response_parse.parse_user_verify_response(ec.userId,ClockType.O.name,response)
                # 如果登录,更新数据库
                new_data = {
                    "user_id": user_id,
                    "user_name": user_name,
                    "dept_id": dept_id,
                    "shift_id": shift_id,
                    "nick_name": nick_name,
                }
                db.update_eap_uer_info(new_data)
                ec.reload_eap_user_login_info()
                update_current_user_id(ec.userId)
                # 上报登出,然后过了1秒之后,再上报登录
                gevent.sleep(1)
                response = eap_request.request_user_verify(user_id, ClockType.I.name)
                response_parse.parse_user_verify_response(user_id, ClockType.I.name, response)

            elif clock_type == ClockType.O.name:
                # 如果是登出,先判断是不是当前用户,如果不是就返回NG
                if ec.userId == user_id:
                    new_data = {
                        "user_id": "",
                        "user_name": "",
                        "dept_id": "",
                        "shift_id": "",
                        "nick_name": "",
                    }
                    response = eap_request.request_user_verify(user_id, ClockType.O.name)
                    response_parse.parse_user_verify_response(user_id, ClockType.O.name, response)
                    db.update_eap_uer_info(new_data)
                    ec.reload_eap_user_login_info()
                    update_current_user_id(ec.userId)
                else:
                    debug_eap(f"收到Eap登出信息,user_id = {user_id} 和 当前的登录的user_id_current = "
                              f"{ec.userId} 不一致,不做登出动作")
            else:
                pass
            result.state = True
            result.data = {
                "clock_type":clock_type,
                "user_id":user_id
            }
            return result
        except Exception as e:
            debug_eap("in deal_user_login_request() error: {}".format(str(e)))
            result.info = "in deal_user_login_request() error: {}".format(str(e))
            return result
        return result

    @classmethod
    def deal_production_info_download(cls, data):
        """
        解析production_down_load的任务队列,这个任务队列是预设队列,就是告诉设备,将有这个工单过来生产,
        需要显保存到电脑上去,然后保存到数据库中去,只保存我需要的信息,其他的信息我不要.
        @param data:
        @return:
        """
        result = DataResult()
        try:
            data = data.get("request_body")
            downloadType = data.get("download_type")
            lotId = data.get("lot_id")
            result.data = lotId  # 这个数据要返回给调用者使用response的时候要调用这个参数
            prodId = data.get("prod_id")
            lotQty = data.get("lot_qty")
            lotShortId = data.get("lot_short_id")
            prodVersion = data.get("prod_version")
            processCode = data.get("process_code")
            useInName = data.get("use_in_name")
            layer = data.get("layer")
            stepSeq = data.get("step_seq")
            routeType = data.get("route_type")
            reworkRouteId = data.get("rework_route_id")
            reworkStepSeq = data.get("rework_step_seq")
            pnlSide = data.get("pnl_side")
            recipeID = data.get("recipe_id")
            faiCount = data.get("fai_count")
            lotInfos = data.get("lot_infos")
            width = None
            height = None

            cropperThick = None
            jointNumber = None
            leftEdge = None
            lotList = lotInfos.get("lot")
            for lot in lotList:
                itemId = lot.get("item_id")
                itemValue = lot.get("item_value")
                if itemId == "S001":
                    height = float(itemValue)
                elif itemId == "S002":
                    width = float(itemValue)
                elif itemId == "S003":
                    cropperThick = float(itemValue)
                elif itemId == "S004":
                    leftEdge = float(itemValue)
                elif itemId == "S005":
                    jointNumber = str(itemValue)

            if width is None:
                result.info = "内板短边尺寸 S002 参数解析失败,没有找到对应的参数"
                return result
            if height is None:
                result.info = "内板长边尺寸S001 参数解析失败,没有找到对应的参数"
                return result
            if cropperThick is None:
                result.info = "铜箔厚度 S003 参数解析失败,没有找到对应的参数"
                return result
            if leftEdge is None:
                result.info = "留边宽度 S004 参数解析失败,没有找到对应的参数"
                return result
            if jointNumber is None:
                result.info = "单层内板数量 S005 参数解析失败,没有找到对应的参数"
                return result

            if width < 200 or width > 1000:
                result.info = f"内板短边尺寸 S002 的值不合法,解析的值为: {width}"
                return result
            if height < 200 or height > 1000:
                result.info = f"内板长边尺寸 S001 的值不合法,解析的值为: {height}"
                return result
            if cropperThick < 0.3 or cropperThick > 4:
                result.info = f"铜箔厚度 S003 参数的值不合法,解析的值为: {cropperThick}"
                return result
            if leftEdge < 0 or leftEdge > 30:
                result.info = f"留边宽度 S004 参数的值不合法,解析的值为:{leftEdge}"
                return result
            if jointNumber != "4" and jointNumber != "6":
                result.info = f"单层内板数量 S005 的值不合法,解析的值为: {jointNumber},这里的值只能是 4 或者 6"
                return result

            newData = {
                "download_type": downloadType,
                "lot_id": lotId,
                "prod_id": prodId,
                "lot_qty": lotQty,
                "lot_short_id": lotShortId,
                "prod_version": prodVersion,
                "process_code": processCode,
                "use_in_name": useInName,
                "layer": layer,
                "step_seq": stepSeq,
                "route_type": routeType,
                "rework_route_id": reworkRouteId,
                "rework_step_seq": reworkStepSeq,
                "pnl_side": pnlSide,
                "recipe_id": recipeID,
                "fai_count": faiCount,
                "width": width,
                "height": height,
                "jointNumber": jointNumber,
                "cropperThick": cropperThick,
                "leftEdge": leftEdge,
                "download_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            }
            if downloadType == DownloadType.A.name:
                newData["key"] = int(time.time())
                db.insert_production_download(newData)
            elif downloadType == DownloadType.D.name:
                db.delete_download_info(query={"lot_id": lotId})
            elif downloadType == DownloadType.U.name:
                db.update_download_info(query={"lot_id": lotId}, newData=newData)
            else:
                debug_eap(f"接收到的prod_down_load 的download_type错误,错误的值为:"
                          f"{downloadType},合法的值为: A(Append), D(Delete), U(Update)")
                result.info = (f"接收到的prod_down_load 的download_type错误,错误的值为:"
                               f"{downloadType},合法的值为: A(Append), D(Delete), U(Update)")
                return result
            update_prodinfo_download_to_ui()
            result.state = True
            return result
        except Exception as e:
            errorInfo = "in deal_production_info_download() error: {}".format(str(e))
            debug_eap(errorInfo)
            result.info = errorInfo
            return result


if __name__ == '__main__':
    eh = EapHandler()
    data = {'request_body': {'port_type': 'Load', 'port_no': '01', 'carrier_id': '', 'port_command': 'Start',
                             'lot_infos': {'lot': [{'lot_id': '11', 'lot_qty': '8', 'pnl_infos': {'pnl': None}}]}}}
    eh.deal_lot_command_download(data)
